Service (Effect)
特定の機能を凝集したcomponentのこと
実装は隠蔽され、Effect内ではinterfaceを介してアクセスできる
以下の組と捉えると良い
Serviceのinterface
③Serviceのimpl
①と②は同時に宣言される
一つの「①と②」に対して、複数の③が存在しうる
interfaceと実体なのでmrsekut.icon
例
Serviceの作成 (①と②を定義)
code:ts
class Random extends Context.Tag("MyRandomService")<
Random, // ①
{ readonly next: Effect.Effect<number> } // ②
() {}
type RandomTag = Random; // Random
type RandomService = Context.Tag.Service<Random>; // { readonly next: Effect.Effect<number>; }
要はインターフェイスのこと
Serviceの利用
この時点では実体は確定していない
code:ts
const program = Effect.gen(function* () {
const random = yield* Random
const randomNumber = yield* random.next
console.log(random number: ${randomNumber})
})
Serviceのimplのinjection
③をinjectionする
code:ts
const runnable = Effect.provideService(program, Random, {
next: Effect.sync(() => Math.random())
})
どういうものをServiceとして定義するか?
以下のように整理できる
injectionの必要がない (=Serviceを使わない)
①-1 ただのeffect関数群
①-2 一つのinstanceを使いまわしたい
injectionの必要はないが、1つのinstanceを使いまわしたい
e.g. AnthoropicBedrockのinstanceを使いまわしたい
injectionしたい (=Serviceを使う)
interfaceを別途定義しなくて良い
interfaceを別途定義する
そもそも、何でもかんでもinjectionする必要がないmrsekut.icon
injectionしたいモチベーションとしてあり得るのは
ライブラリのインスタンスをシングルトンにしたい時
e.g. アプリケーション全体で、一意のPrisma Clientを使いたい
同一のインターフェイスを用意して、実装を入れ替えたい時
e.g. モックに入れ替えるようなテストを書いている
e.g. 特定のライブラリの依存せず、同一インターフェイスの代替品に入れ替え可能にする
Serviceの問題点?
メモ化?
DIPできない?
DIPってそもそもなんで重要なんだっけ
code:ts
const program = Effect.gen(function* () {
const maybeRandom = yield* Effect.serviceOption(Random)
const randomNumber = Option.isNone(maybeRandom)
? // the service is not available, return a default value
-1
: // the service is available
yield* maybeRandom.value.next
console.log(randomNumber)
})
defaultで入っている5つのServie
code:ts
type DefaultServices = Clock | ConfigProvider | Console | Random | Tracer
例
code:ts
// program :: Effect<void, never, never>
const program = Effect.gen(function* () {
const now = yield* Clock.currentTimeMillis
yield* Console.log(Application started at ${new Date(now)})
})
Effect.runFork(program)
まじかよ、変やろ、お節介すぎるmrsekut.icon
だって型がおかしいやん
上書きもできる